package com.tplhk.redis;


import com.tplhk.redis.lock.jedis.util.RedisLockThreadLocalContext;
import com.tplhk.redis.lock.jedis.util.RedisTool;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static java.lang.Thread.sleep;

@SpringBootTest(classes = RedisApplication.class)
public class RedisLockDemo {
    //随便弄个key的名字
    private static final String LOCK_KEY = "distributedLock:key";

    //业务过期时长
    private static final int EXPIRE_TIME_SECONDS = 10;

    //延期时长
    private static final int DELAY_TIME_SECONDS = 5;

    //守护线程间隔单位时间进行一次续约：建议设置为过期时长少3秒左右
    private static final int CHECK_DELAY_TIME_SECONDS = EXPIRE_TIME_SECONDS - 3;

    @Autowired
    JedisPool jedisPool;

    @Test
    public void testRedisLock() throws InterruptedException {

        //开启50个工作线程，模拟分布式服务中的服务，每个服务业务时间为60秒
        for (int i = 0; i < 50; i++) {
            startAWork(String.valueOf(i), 60);
        }
        sleep(70 * 1000);
    }

    /**
     * 开启一个工作线程，模拟分布式中的一个服务，抢分布式锁
     *
     * @param threadName   线程名称
     * @param lengthOfWork 工作时长 秒
     */
    public void startAWork(String threadName, int lengthOfWork) {
        new Thread(() -> {

            try {
                //生成并保存 获取分布式锁的 请求id，解决问题二
                String requestId = UUID.randomUUID().toString();
                RedisLockThreadLocalContext.getThreadLocal().set(requestId);

                //获取分布式锁，设置过期时间2s，解决问题一
                boolean result = RedisTool.tryGetDistributedLock(getJedis(), LOCK_KEY, requestId, EXPIRE_TIME_SECONDS);

                if (result) {//如果成功获取到锁
                    //开一个守护线程延长锁的过期时间
                    Thread thread = new Thread(() -> {
                        while (true) {
                            Jedis jedis = getJedis();
                            try {
                                // 此守护线程间隔多长时间续约，一般设置为过期时间
                                TimeUnit.SECONDS.sleep(CHECK_DELAY_TIME_SECONDS);
                                System.out.println("守护线程延长锁的过期时间10s = " + jedis.getDB());

                                jedis.setex(LOCK_KEY, DELAY_TIME_SECONDS, requestId);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } finally {
                                if (jedis != null) {
                                    jedis.close();
                                }
                            }
                        }
                    });
                    thread.setDaemon(true);
                    thread.start();

                    System.out.println("线程" + threadName + "拿到锁，干点事情...........................");
                    //睡眠一定时间，模拟业务耗时
                    TimeUnit.SECONDS.sleep(lengthOfWork);
                } else {
                    System.out.println("线程" + threadName + "没有拿到锁");
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                //释放分布式锁
                String requestId = RedisLockThreadLocalContext.getThreadLocal().get();
                boolean result = RedisTool.releaseDistributedLock(getJedis(), LOCK_KEY, requestId);
                if (result) {
                    System.out.println("线程" + threadName + "释放锁....................");
                } else {
                    System.out.println("线程" + threadName + "释放锁失败");
                }

            }
            System.out.println("线程" + threadName + "结束");
        }).start();
    }


    public Jedis getJedis() {
        Jedis jedis = jedisPool.getResource();
        jedis.select(1);
        return jedis;
    }

}
